package net.wzw.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import net.wzw.constant.RedisKeyConstant;
import net.wzw.converter.FoodConverter;
import net.wzw.entity.*;
import net.wzw.enums.*;
import net.wzw.exception.BizException;
import net.wzw.interceptor.LoginInterceptor;
import net.wzw.mapper.FoodMapper;
import net.wzw.model.FoodDO;
import net.wzw.mq.FoodUpdateMQMessage;
import net.wzw.redis.RedisCache;
import net.wzw.redis.RedisLock;
import net.wzw.service.FoodService;
import net.wzw.utils.ApiResult;
import net.wzw.utils.JsonUtil;
import net.wzw.vo.FoodVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author wzw
 * @date 2025/4/19
 * @Description
 */
@Service
@Slf4j
public class FoodServiceImpl implements FoodService {


    /**
     * 注入美食 mapper
     */
    @Autowired
    private FoodMapper foodMapper;

    /**
     * 注入美食转换器
     */
    @Autowired
    private FoodConverter foodConverter;

    /**
     * 注入 redis 缓存工具类
     */
    @Autowired
    private RedisCache redisCache;

    /**
     * 注入 redis
     */
    @Autowired
    private RedisLock redisLock;


    /**
     * 获取美食信息
     * @param foodId
     * @return
     */
    @Override
    public FoodVO getFoodInfo(Long foodId) {
        log.info("美食模块-从数据库获取美食信息，foodId:{}", foodId);
        FoodDO foodDO = new FoodDO();
        // 先从缓存中获取美食信息
        foodDO = getFoodInfoFromCache(foodId);
        // 缓存为空
        if (foodDO == null) {
            // 从数据库中获取美食信息
            foodDO = getFoodInfoFromDB(foodId);
            // 如果为空，则返回空
            if (foodDO == null) {
                return null;
            }
        }

        // 这里我们返回 foodVO 的信息, 需要通过拷贝 foodDO 到 foodVO 中
        FoodVO foodVO = new FoodVO();
        if (foodVO.getFoodCateTag() != null) {
            // 设置美食分类
            foodVO.setFoodCateTag(FoodCateTag.getByTag(foodDO.getFoodCateTag()).getTagValue());
        }
        if (foodVO.getFoodDifficulty() != null) {
            // 设置美食制作难度
            foodVO.setFoodDifficulty(FoodDifficulty.getByDifficulty(foodDO.getFoodDifficulty()).getDiffucultyValue());
        }
        if (foodVO.getFoodTime() != null) {
            // 设置美食制作时长
            foodVO.setFoodTime(FoodTime.getByTime(foodDO.getFoodTime()).getTimeValue());
        }
        if (StringUtils.isNotBlank(foodDO.getFoodDetail())) {
            // 设置美食详情
            List<FoodStepDetailEntity> stepDetails = JsonUtil.StringtoList(foodDO.getFoodDetail(), FoodStepDetailEntity.class);
            foodVO.setFoodStepDetailEntityList(stepDetails);
        }
        if (StringUtils.isNotBlank(foodDO.getFoodList())) {
            // 设置美食清单
            List<FoodListEntity> foodList = JsonUtil.StringtoList(foodDO.getFoodList(), FoodListEntity.class);
            foodVO.setFoodList(foodList);
        }
        if (StringUtils.isNotBlank(foodDO.getFoodSkuids())) {
            // TODO 商品信息关联
            List<Long> skuIds = JsonUtil.jsonToLongList(foodDO.getFoodSkuids());
            // 设置美食关联商品id
            foodVO.setSkuIds(skuIds);
        }
        BeanUtils.copyProperties(foodDO, foodVO);
        return foodVO;

    }

    /**
     * 从缓存中读取美食详情信息
     * @param foodId
     * @return
     */
    private FoodDO getFoodInfoFromCache(Long foodId) {
        String foodKey = RedisKeyConstant.FOOD_INFO_PREFIX + foodId;
        // 从缓存中获取数据
        String foodValue = redisCache.get(foodKey);
        log.info("美食模块-从缓存获取美食信息，foodKey:{}，foodValue:{}", foodKey, foodValue);
        if (org.springframework.util.StringUtils.hasLength(foodValue)) {
            // 当读到空数据时返回空，防止缓存穿透，直接返回null
            if (Objects.equals(redisCache.EMPTY_CACHE, foodValue)) {
                return null;
            }

            // 否则读取到缓存后，转换成对象之后返回
            FoodDO foodDO = JsonUtil.json2Object(foodValue, FoodDO.class);
            // 设置延期
            Long expire = redisCache.getExpire(foodKey, TimeUnit.SECONDS);
            // 如果过期时间已经在 1 小时内了就自动延期
            if (expire < RedisCache.ONE_HOURS_SECONDS) {
                // 缓存精准自动延期 2天 + 随机几个小时
                redisCache.expire(foodKey, RedisCache.generateCacheExpire());
            }
            return foodDO;
        }

        return null;
    }

    /**
     * 从数据库中读取美食详情信息
     * @param foodId
     * @return
     */
    private FoodDO getFoodInfoFromDB(Long foodId) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        // 这里增加分布式锁进行互斥，同时间只能一个用户成功，避免重复请求
        String foodUpdateLockKey = RedisKeyConstant.FOOD_UPDATE_LOCK_PREFIX + loginUser.getId();
        boolean lock = false;

        try {
            // 尝试加锁，超时时间为 200 ms，超时后自动释放锁
            lock = redisLock.tryLock(foodUpdateLockKey, RedisCache.USER_UPDATE_LOCK_TIMEOUT);
        } catch (InterruptedException e) {
            // 加锁异常后，再次尝试从缓存中获取，如果缓存存在直接返回
            FoodDO foodDO = getFoodInfoFromCache(foodId);
            if (foodDO != null) {
                return foodDO;
            }
            log.error("美食模块-从数据库获取美食详情尝试加锁异常，异常信息：{}，异常栈信息：{}", e.getMessage(), e);
            throw new BizException(BizCodes.FOOD_INFO_LOCK_FAIL);
        }

        // 加锁失败
        if (!lock) {
            // 加锁异常后，再次尝试从缓存中获取，如果缓存存在直接返回
            FoodDO foodDO = getFoodInfoFromCache(foodId);
            if (foodDO != null) {
                return foodDO;
            }
            log.info("美食模块-美食详情缓存为空，从数据库获取美食详情数据获取锁失败，foodId:{}，user:{}", foodId, loginUser.getId());
            throw new BizException(BizCodes.FOOD_INFO_LOCK_FAIL);
        }

        try {
            // 极端情况下: 查数据库前再次尝试从缓存中获取，如果缓存存在直接返回
            FoodDO foodDO = getFoodInfoFromCache(foodId);
            if (foodDO != null) {
                return foodDO;
            }
            log.info("美食模块-美食缓存详情为空，从数据库获取美食信息，foodId:{}，user:{}", foodId, loginUser.getId());
            // 从数据库中获取美食详情
            FoodDO dto = foodMapper.selectOne(new QueryWrapper<FoodDO>().eq("id", foodId));
            // 美食详情缓存 key
            String foodKey = RedisKeyConstant.FOOD_INFO_PREFIX + foodId;
            // 如果从数据库中读取为空数据
            if (Objects.isNull(dto)) {
                // 设置一个空数据到缓存，过期时间为穿透的过期时间，30~100秒
                redisCache.setCache(foodKey, redisCache.EMPTY_CACHE, RedisCache.generateCachePenetrationExpire());
                return null;
            }
            // 写入缓存，缓存过期时间 2天 + 随机几小时
            redisCache.setCache(foodKey, dto, RedisCache.generateCacheExpire());
            return dto;
        } finally {
            // 释放锁
            redisLock.unlock(foodUpdateLockKey);
        }
    }


    /**
     * 分页查询我的美食列表
     * @param foodQueryEntity
     * @return
     */
    @Override
    public Map<String, Object> getFoodList(FoodQueryEntity foodQueryEntity) {
        // 获取用户信息
        // TODO 后续使用 Redis 分布式缓存替代
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        log.info("美食模块-从数据库获取美食列表参数信息，page:{}，size:{}，uid:{}", foodQueryEntity.getPage(), foodQueryEntity.getPageSize(), loginUser.getId());

        // 先从缓存分页读取
        Map<String, Object> pageCache = getFoodListFromCache(foodQueryEntity, loginUser);

        if (pageCache != null) {

            return pageCache;
        }

        // 缓存没命中的话 从数据库查询
        return getFoodListFromDB(foodQueryEntity, loginUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResult saveOrUpdate(FoodSaveOrUpdateEntity foodSaveOrUpdateEntity) {

        LoginUser loginUser = LoginInterceptor.threadLocal.get();

        //增加分布式锁，同一个用户同时间只能操作一次，避免重复请求

        String foodUpdateLockKey = RedisKeyConstant.FOOD_UPDATE_LOCK_PREFIX+ loginUser.getId();

        Boolean lock = null;

        //新增没必要加锁,只有更新时才需要加锁
        if(foodSaveOrUpdateEntity.getId() !=null && foodSaveOrUpdateEntity.getId() >0){

           lock =  redisLock.lock(foodUpdateLockKey);

        }

        if(lock!= null && !lock){

            log.info("美食服务模块-新增/修改美食信息获取锁失败,user:{}",loginUser.getId());

            throw  new BizException(BizCodes.USER_UPDATE_LOCK_FAIL);
        }
        try {

            //构建美食信息
            FoodDO foodDO = bulidFoodDO(foodSaveOrUpdateEntity,loginUser);

            Boolean isAdd = null;
            //保存美食信息
            if(foodDO.getId() == null){

                isAdd = true;
                // 新增
                int rows = foodMapper.insert(foodDO);

                log.info("美食模块-新增美食信息:rows={},data={}",rows,foodDO);



            }else {

                //修改
                foodMapper.update(foodDO,new QueryWrapper<FoodDO>().eq("id",foodDO.getId()));
                log.info("美食模块-修改美食信息:data={}",foodDO);
            }
            //跟新美食缓存
            updateFoodCache(foodDO,loginUser,isAdd);
            //发布美食数据更新事件消息
            publishFoodUpdatedEvent(foodDO);

            return null;
        }finally {

            if(lock != null){
                //释放锁
                redisLock.unlock(foodUpdateLockKey);

            }
        }



    }

    /*** 更新美食缓存
     * @param foodDO
     * @param loginUser
     * @param isAdd
     */
    private void updateFoodCache(FoodDO foodDO,LoginUser loginUser,Boolean isAdd){

        //修改美食信息缓存数据 ,过期时间为2天 + 随机几小时
       String foodKey =  RedisKeyConstant.FOOD_INFO_PREFIX + foodDO.getId();

       redisCache.setCache(foodKey,foodDO,RedisCache.generateCacheExpire());

       //只有新增的时候去累计，修改的时候不累计,这样就可以统计出一个靳准的我的美食列表总数
        if(isAdd!=null && isAdd){
            //美食博主的美食列表总数
           String userFoodTotalKey =  RedisKeyConstant.USER_FOOD_TOTAL_PRXFIX+loginUser.getId();
            //自增计算
            redisCache.increment(userFoodTotalKey,1);

        }
    }

   /**
    *  发布美食更新时间
    * **/
    private void publishFoodUpdatedEvent(FoodDO foodDO){

        //发消息通知博主的美食信息发生变更
        //构建消息
       FoodUpdateMQMessage message = FoodUpdateMQMessage.builder().
               foodId(foodDO.getId()).userId(foodDO.getUserId()).build();
       log.info("美食博主变更美食信息:{}",message);
       //发送MQ消息

    }

    private FoodDO bulidFoodDO(FoodSaveOrUpdateEntity foodSaveOrUpdateEntity,LoginUser loginUser){

       FoodDO foodDO =  foodConverter.convertFoodDO(foodSaveOrUpdateEntity);
       //设置美食食材清单列表
        foodDO.setFoodList(JsonUtil.object2Json(foodSaveOrUpdateEntity.getFoodList()));
        //设置美食详情
        foodDO.setFoodDetail(JsonUtil.object2Json(foodSaveOrUpdateEntity.getFoodDetail()));
        //设置用户id
        foodDO.setUserId(loginUser.getId());

        // 新增数据
        if(foodDO.getId() == null){

            // 菜谱状态为空,则设置为删除
            if(Objects.isNull(foodDO.getFoodStatus())){

                foodDO.setFoodStatus(DeleteStatus.NO_DEL.getStatus());
            }

            foodDO.setFoodCreatetime(new Date());

        }

        foodDO.setFoodUpdatetime(new Date());

        return foodDO;

    }




    /**
     * 从缓存中分页获取我的美食列表
     * @param foodQueryEntity
     * @param loginUser
     * @return
     */
    private Map<String, Object> getFoodListFromCache(FoodQueryEntity foodQueryEntity, LoginUser loginUser) {
        // 先从缓存分页读取
        String userFoodPageKey = RedisKeyConstant.USER_FOOD_PAGE_PREFIX + loginUser.getId() + ":" + foodQueryEntity.getPage();

        String foodsCache = redisCache.get(userFoodPageKey);
        // 判断缓存是否为空
        if (foodsCache != null && !"".equals(foodsCache)) {
            // 计算我的美食列表总数（这里会精准统计新增美食的总数，相当于直接查数据库列表）
            String userFoodTotalKey = RedisKeyConstant.USER_FOOD_TOTAL_PRXFIX + loginUser.getId();

            int size = redisCache.getInt(userFoodTotalKey);
            // 解析缓存结果列表
            List<FoodVO> foodVOList = JSON.parseObject(foodsCache, List.class);

            Long expire = redisCache.getExpire(userFoodPageKey, TimeUnit.SECONDS);
            // 如果过期时间已经在 1 小时内了就自动延期
            if (expire < RedisCache.ONE_HOURS_SECONDS) {
                // 缓存精准自动延期 2天 + 随机几个小时
                redisCache.expire(userFoodPageKey, RedisCache.generateCacheExpire());
            }

            // 处理分页返回数据
            Map<String,Object> pageMap = new HashMap<>(5);
            // 总条数
            pageMap.put("total", size);

            int totalPages = (int) Math.ceil((double) size / foodQueryEntity.getPageSize());// 计算总页数
            // 总分页数
            pageMap.put("pages", totalPages);
            // 当前页
            pageMap.put("curr_page", foodQueryEntity.getPage());
            // 每页条数
            pageMap.put("curr_page_size", foodQueryEntity.getPageSize());
            // 列表信息
            pageMap.put("list", foodVOList);

            return pageMap;
        }

        return null;
    }


    /**
     * 从数据库中分页查询我的美食列表
     * @param foodQueryEntity
     * @param loginUser
     * @return
     */
    private Map<String, Object> getFoodListFromDB(FoodQueryEntity foodQueryEntity, LoginUser loginUser) {
        // 这里增加分布式锁，同一个用户同时间只能操作一次，避免重复请求
        String foodUpdateLockKey = RedisKeyConstant.FOOD_UPDATE_LOCK_PREFIX + loginUser.getId();

        boolean lock = false;

        try {
            // 尝试加锁，超时时间为 200 ms，超时后自动释放锁
            lock = redisLock.tryLock(foodUpdateLockKey, RedisCache.USER_UPDATE_LOCK_TIMEOUT);

        } catch (InterruptedException e) {
            // 加锁异常后，再次尝试从缓存中获取，如果缓存存在直接返回
            Map<String, Object> pageCache = getFoodListFromCache(foodQueryEntity, loginUser);

            if (pageCache != null) {

                return pageCache;
            }

            log.error("美食模块-尝试加锁异常，异常信息：{}，异常栈信息：{}", e.getMessage(), e);

            throw new BizException(BizCodes.FOOD_INFO_LOCK_FAIL);
        }

        // 加锁失败
        if (!lock) {
            // 加锁异常后，再次尝试从缓存中获取，如果缓存存在直接返回
            Map<String, Object> pageCache = getFoodListFromCache(foodQueryEntity, loginUser);
            if (pageCache != null) {
                return pageCache;
            }
            log.info("美食模块-美食分页缓存为空，从数据库获取美食分页数据获取锁失败，user:{}", loginUser.getId());
            throw new BizException(BizCodes.FOOD_INFO_LOCK_FAIL);
        }

        try {
            // 极端情况下: 查数据库前再次尝试从缓存中获取，如果缓存存在直接返回
            Map<String, Object> pageCache = getFoodListFromCache(foodQueryEntity, loginUser);
            if (pageCache != null) {
                return pageCache;
            }

            // 分页信息
            Page<FoodDO> pageInfo = new Page<>(foodQueryEntity.getPage(), foodQueryEntity.getPageSize());

            IPage<FoodDO> foodDOPage = null;

            // 查询我的未删除美食列表
            foodDOPage = foodMapper.selectPage(pageInfo, new QueryWrapper<FoodDO>().eq("user_id", loginUser.getId()).eq("food_status", FoodStatus.DEFAULT_STATUS));
            List<FoodDO> foodDOList = foodDOPage.getRecords();
            log.info("美食模块-从数据库获取我的美食列表，page:{}，total:{}，size：{}", foodDOPage.getPages(), foodDOPage.getTotal(), foodDOPage.getSize());

            // 对象流式转换处理美食列表结果信息
            List<FoodVO> foodVOList = foodDOList.stream().map(obj -> {
                FoodVO foodVO = new FoodVO();
                if (obj.getFoodCateTag() != null) {
                    // 设置美食分类
                    foodVO.setFoodCateTag(FoodCateTag.getByTag(obj.getFoodCateTag()).getTagValue());
                }
                if (obj.getFoodDifficulty() != null) {
                    // 设置美食制作难度
                    foodVO.setFoodDifficulty(FoodDifficulty.getByDifficulty(obj.getFoodDifficulty()).getDiffucultyValue());
                }
                if (obj.getFoodTime() != null) {
                    // 设置美食制作时长
                    foodVO.setFoodTime(FoodTime.getByTime(obj.getFoodTime()).getTimeValue());
                }
                if (StringUtils.isNotBlank(obj.getFoodDetail())) {
                    // 设置美食详情
                    List<FoodStepDetailEntity> stepDetails = JsonUtil.StringtoList(obj.getFoodDetail(), FoodStepDetailEntity.class);
                    foodVO.setFoodStepDetailEntityList(stepDetails);
                }
                if (StringUtils.isNotBlank(obj.getFoodList())) {
                    // 设置美食清单
                    List<FoodListEntity> foodList = JsonUtil.StringtoList(obj.getFoodList(), FoodListEntity.class);
                    foodVO.setFoodList(foodList);
                }
                // TODO 商品信息关联
                if (StringUtils.isNotBlank(obj.getFoodSkuids())) {
                    List<Long> skuIds = JsonUtil.jsonToLongList(obj.getFoodSkuids());
                    // 设置美食关联商品id
                    foodVO.setSkuIds(skuIds);
                }
                BeanUtils.copyProperties(obj, foodVO);
                return foodVO;
            }).collect(Collectors.toList());

            // 写入分页缓存数据
            String userFoodPageKey = RedisKeyConstant.USER_FOOD_PAGE_PREFIX + loginUser.getId() + ":" + foodQueryEntity.getPage();
            redisCache.set(userFoodPageKey, JsonUtil.object2Json(foodVOList), RedisCache.generateCacheExpire());

            Map<String, Object> pageMap = new HashMap<>(5);
            // 总条数
            pageMap.put("total", foodDOPage.getTotal());
            // 总分页数
            pageMap.put("pages", foodDOPage.getPages());
            // 当前页
            pageMap.put("curr_page", foodQueryEntity.getPage());
            // 每页条数
            pageMap.put("curr_page_size", foodQueryEntity.getPageSize());
            // 列表信息
            pageMap.put("list", foodVOList);

            return pageMap;
        } finally {
            // 释放锁
            redisLock.unlock(foodUpdateLockKey);
        }
    }

}




